// 《围棋》作者版权所有。保留所有权利。
// 此源代码的使用受BSD样式
// 许可证的约束，该许可证可以在许可证文件中找到。

// Test2json将go测试输出转换为机器可读的JSON流。
// 
// 用法：
// 
// go工具test2json[-p pkg][t][./pkg.test-test.v[-test.paniconexit0]]
// test2json运行给定的测试命令并将其输出转换为JSON；
// 如果没有指定命令，test2json希望标准输入上有测试输出。
// 它将相应的JSON事件流写入标准输出。
// 没有不必要的输入或输出缓冲，因此可以读取JSON流以“实时更新”测试状态。
// 
// p标志设置每个测试事件中报告的包。
// 
// t标志要求向每个测试事件添加时间戳。
// 
// 必须使用-test调用测试。v、 另外通过
// /-测试。如果正在运行的测试之一调用os，则paniconexit0将导致test2json以非零
// 状态退出。退出（0）。
// 
// 请注意，test2json仅用于转换单个测试
// 二进制文件的输出。要转换“go test”命令的输出，
// 使用“go test-json”，而不是直接调用test2json。
// 
// 输出格式
// 
// JSON流是一个以新行分隔的TestEvent对象序列
// 对应于Go结构：
// 
// 键入TestEvent struct{
// Time Time.Time
// 操作字符串
// 包字符串
// 测试字符串
// 经过的浮点64
// 输出字符串
// /}
// 时间字段保存事件发生的时间。
// 对于缓存的测试结果，它通常被省略。
// 
// 操作字段是一组固定的操作描述之一：
// 
// 运行-测试已开始运行
// 暂停-测试已暂停
// 继续-测试已继续运行
// 通过-测试通过
// 测试台-基准打印的日志输出，但未失败
// 失败-测试或基准测试失败
// 输出-测试打印输出
// 跳过-跳过测试或包不包含测试
// 
// 包字段（如果存在）指定要测试的包。
// 当go命令以-json模式运行并行测试时，来自
// 的事件会交错进行不同的测试；Package字段允许读卡器
// 将它们分开。
// 
// 测试字段（如果存在）指定导致事件的测试、示例或基准
// 函数。整个包测试的事件
// 不设置测试。
// 
// 已用字段设置为“通过”和“失败”事件。它给出了通过或失败的特定测试或整个包测试所经过的时间。
// 
// 输出字段设置为Action==“Output”，是测试输出的一部分
// （标准输出和标准错误合并在一起）。输出为
// 未经修改，但测试的无效UTF-8输出通过使用替换字符强制
// 为有效UTF-8。除此之外，所有输出事件的输出字段的串联就是测试执行的确切输出。
// 
// 当基准运行时，它通常会生成一行输出
// 给出计时结果。在一个Action==“output”
// 且无测试字段的事件中报告该行。如果基准记录输出或报告故障
// （例如，通过使用b.Log或b.Error），则额外的输出将作为一系列事件报告
// ，测试设置为基准名称，并通过最后一个事件终止
// ，操作=“bench”或“fail”。
// 基准测试没有操作==“运行”、“暂停”或“继续”的事件。
// 
package main

import (
	"flag"
	"fmt"
	exec "internal/execabs"
	"io"
	"os"

	"cmd/internal/test2json"
)

var (
	flagP = flag.String("p", "", "report `pkg` as the package being tested in each event")
	flagT = flag.Bool("t", false, "include timestamps in events")
)

func usage() {
	fmt.Fprintf(os.Stderr, "usage: go tool test2json [-p pkg] [-t] [./pkg.test -test.v]\n")
	os.Exit(2)
}

func main() {
	flag.Usage = usage
	flag.Parse()

	var mode test2json.Mode
	if *flagT {
		mode |= test2json.Timestamp
	}
	c := test2json.NewConverter(os.Stdout, *flagP, mode)
	defer c.Close()

	if flag.NArg() == 0 {
		io.Copy(c, os.Stdin)
	} else {
		args := flag.Args()
		cmd := exec.Command(args[0], args[1:]...)
		w := &countWriter{0, c}
		cmd.Stdout = w
		cmd.Stderr = w
		err := cmd.Run()
		if err != nil {
			if w.n > 0 {
				// 假设命令打印了失败的原因。
			} else {
				fmt.Fprintf(c, "test2json: %v\n", err)
			}
		}
		c.Exited(err)
		if err != nil {
			c.Close()
			os.Exit(1)
		}
	}
}

type countWriter struct {
	n int64
	w io.Writer
}

func (w *countWriter) Write(b []byte) (int, error) {
	w.n += int64(len(b))
	return w.w.Write(b)
}
